import 'dart:io';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import 'fade_route.dart';

// PS：navigate2.0更符合声明式，操控页面列表数据驱动栈更新，显示当前页面，而不是声明式调用
// 参考：https://blog.csdn.net/yingshukun/article/details/120660992

// 0. 即插即用，直接赋值就走
// 1. 跳转原生：可以支持、混合栈、见原生多flutter view use 1 engine
// 2. 登陆拦截：可以支持，但复杂操作侵入性太高，最好不要支持
// 3. 底部导航路由：已支持，必须使用name的路由打开
// 4. 正常入栈出栈by route name支持，unknown page支持
enum RouteType {
  cupertino,
  material,
  fade,
  // other custom
}

enum OpenType {
  push,
  popAndPush,
  pushReplacement,
}

const _navKey = GlobalObjectKey<NavigatorState>('nav');

get navKey => _navKey;

final router = Router();

typedef OnBottomNavigate = Function(String targetAddress);

class Router {
  Router._internal();

  static final Router _singleton = Router._internal();

  factory Router() => _singleton;

  String? _loginPageRouteAddress;
  List<RouteBuilder> _allPages = [];
  List<String>? _topLevelPages;
  Widget? _unknownPage;

  // top-level pages' container
  String? _initialRouteAddress;
  Function? _checkLogin;

  OnBottomNavigate? _onBottomNavigate;

  set onBottomNavigate(OnBottomNavigate onBottomNavigate) {
    _onBottomNavigate = onBottomNavigate;
  }

  // must init
  // login intercept, refer needLogin param.
  // topLevelPage, means bottom tab page, need to be intercepted before route
  void initRoute(
    List<RouteBuilder> allPages,
    Widget unknownPage, {
    String? loginPageRouteAddress,
    List<String>? topLevelPages,
    String? initialRouteAddress,
    Function? checkLogin,
  }) {
    _unknownPage = unknownPage;
    _checkLogin = checkLogin;
    _allPages = allPages;
    _initialRouteAddress = initialRouteAddress;
    if (null != loginPageRouteAddress) {
      _loginPageRouteAddress = loginPageRouteAddress;
      assert(_checkPageExist(loginPageRouteAddress), "please set loginPage in initRoute");
    }
    _topLevelPages = topLevelPages;
  }

  PageRoute pageRoute(RouteSettings routeSettings) {
    RouteParams param;
    if (null == routeSettings.arguments) {
      param = RouteParams();
    } else {
      param = routeSettings.arguments as RouteParams;
    }

    Widget page = _routeMap(routeSettings.name ?? "", param.args ?? <String, dynamic>{});

    switch (param.routeType) {
      case RouteType.cupertino:
        return CupertinoPageRoute(builder: (_) => page, settings: routeSettings);
      case RouteType.material:
        return MaterialPageRoute(builder: (_) => page, settings: routeSettings);
      case RouteType.fade:
        return FadeRoute(page, routeSettings);
      default:
        // default route type: ios android can be split
        if (Platform.isAndroid) {
          return FadeRoute(page, routeSettings);
        } else if (Platform.isIOS) {
          return CupertinoPageRoute(builder: (_) => page, settings: routeSettings);
        } else {
          return MaterialPageRoute(builder: (_) => page, settings: routeSettings);
        }
    }
  }

  Future<T?> openFlutterPage<T extends Object?>(
    String targetRouteAddress, {
    Map<String, dynamic>? args,
    RouteType? routeType,
    OpenType? openType,
    // only support intercept, not support return value
    bool? needLogin,
  }) async {
    final routeParams =
        RouteParams(fromCtx: _navKey.currentContext, routeType: routeType, args: args);

    // intercept login.
    if (true == needLogin) {
      assert(null != _loginPageRouteAddress, "please set loginPageRouteAddress in initRoute");
      assert(null != _checkLogin, "please set checkLogin function in initRoute");
      bool isLogin = await _checkLogin?.call();
      if (null != _loginPageRouteAddress && true != isLogin) {
        return _pushPage(_loginPageRouteAddress ?? "", routeParams: routeParams);
      }
    }

    // intercept top-level page route
    if (true == _topLevelPages?.isNotEmpty && _isTopLevelPage(targetRouteAddress)) {
      // pop until app entry page
      assert(_checkPageExist(_initialRouteAddress ?? ""), "please set topLevelPage in initRoute");
      _navKey.currentState?.popUntil(ModalRoute.withName(_initialRouteAddress ?? ""));
      assert(null != _onBottomNavigate, "please set onBottomNavigate callback in initRoute");
      _onBottomNavigate?.call(targetRouteAddress);
      return null;
    }

    // normal route
    switch (openType) {
      case OpenType.push:
        return _pushPage(targetRouteAddress, routeParams: routeParams);
      case OpenType.popAndPush:
        return _popAndPushPage(targetRouteAddress, routeParams: routeParams);
      case OpenType.pushReplacement:
        return _pushPageAndReplacement(targetRouteAddress, routeParams: routeParams);
      default:
        return _pushPage(targetRouteAddress, routeParams: routeParams);
    }
  }

  Future<T?> _pushPage<T extends Object?>(
    String targetRouteAddress, {
    RouteParams? routeParams,
  }) async {
    return _navKey.currentState?.pushNamed<T>(targetRouteAddress, arguments: routeParams);
  }

  Future<T?> _popAndPushPage<T extends Object?>(
    String targetRouteAddress, {
    RouteParams? routeParams,
  }) async {
    return _navKey.currentState?.popAndPushNamed(targetRouteAddress, arguments: routeParams);
  }

  Future<T?> _pushPageAndReplacement<T extends Object?>(
    String targetRouteAddress, {
    RouteParams? routeParams,
  }) async {
    return _navKey.currentState?.pushReplacementNamed(targetRouteAddress, arguments: routeParams);
  }

  // unsafe if the target page not be managed by router
  // void popPageButUnSafe<T extends Object?>({T? popReturnValue}) {
  //   _navKey.currentState?.pop(popReturnValue);
  // }

  // safe
  void popPage<T extends Object?>(
    BuildContext currentCtx, {
    T? popReturnValue,
  }) {
    Navigator.maybeOf(currentCtx)?.pop(popReturnValue);
  }

  void popPagesUntil(
    BuildContext currentCtx,
    String targetRouteAddress,
  ) {
    Navigator.maybeOf(currentCtx)?.popUntil(ModalRoute.withName(targetRouteAddress));
  }

  Widget _routeMap(String routeAddress, Map<String, dynamic> args) {
    for (int i = 0; i < _allPages.length; i++) {
      final page = _allPages[i];
      if (routeAddress == page.routeAddress) {
        return page.pageBuilder(args);
      }
    }
    return _unknownPage ?? const Text("known page", style: TextStyle(fontSize: 50));
  }

  bool _checkPageExist(String targetRouteAddress) {
    for (int i = 0; i < _allPages.length; i++) {
      final page = _allPages[i];
      if (targetRouteAddress == page.routeAddress) {
        return true;
      }
    }
    return false;
  }

  bool _isTopLevelPage(String targetRouteAddress) {
    for (int i = 0; i < (_topLevelPages?.length ?? 0); i++) {
      final page = _topLevelPages?[i];
      if (targetRouteAddress == page) {
        return true;
      }
    }
    return false;
  }
}

typedef PageBuilder = Widget Function(Map<String, dynamic> args);

class RouteBuilder {
  String routeAddress;
  PageBuilder pageBuilder;

  RouteBuilder(this.routeAddress, this.pageBuilder);
}

class RouteParams {
  BuildContext? fromCtx;
  Map<String, dynamic>? args;
  RouteType? routeType;

  RouteParams({this.fromCtx, this.args, this.routeType});
}
